#include "format.h"

#include <string>
#include <cmath>
#include <vector>
#include <cstring>

namespace fm {
    static char *search(char *s, char ch) {
        for (uint64_t i = 0; ; i ++ ) {
            char c = s[i];

            if (c == ch || c == '\0' || c == '\\') {
                return s + i;
            }
        }
    }

    const int MODE_RIGHT = 0;
    const int MODE_LEFT = 1;
    const int MODE_MID = 2;

    static void makeWidth(std::string & res, const char *s, uint64_t width, char mode, char fill) {
        uint64_t sl = strlen(s);

        if (sl >= width) {
            res += s;
            return;
        }

        if (mode == MODE_LEFT) {
            res += s;
            for (int i = 0; i < width - sl; i ++) {
                res += fill;
            }
        } else if (mode == MODE_RIGHT){
            for (int i = 0; i < width - sl; i ++) {
                res += fill;
            }
            res += s;
        } else if (mode == MODE_MID) {
            int ll, rl;

            ll = (width - sl) / 2;
            rl = ll;
            ll += (width - sl - rl * 2);

            for (uint64_t i = 0; i < ll; i ++) {
                res += fill;
            }

            res += s;

            for (uint64_t i = 0; i < rl; i ++) {
                res += fill;
            }
        }
    }

    static void makeWidth(std::string & res, const char *s, std::vector<std::string> & infos) {
        if (infos.size() == 0) {
            res += s;
            return;
        }

        uint64_t width = 0;
        char mode = MODE_RIGHT;
        char fill = ' ';

        for (uint64_t i = 0; i < infos.size(); i ++) {
            std::string & info = infos[i];

            if (info[0] == 'w') {
                sscanf(info.c_str(), "w%llu", &width);
            } else if (info[0] == 'f') {
                if (info == "f")
                    fill = ',';
                else
                    sscanf(info.c_str(), "f%c", &fill);
            } else if (info == "<") {
                mode = MODE_LEFT;
            } else if (info == ">") {
                mode = MODE_RIGHT;
            } else if (info == "^") {
                mode = MODE_MID;
            }
        }

        makeWidth(res, s, width, mode, fill);
    }

    static void addStr(std::string & s, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        switch (obj_type(vm, o)) {
        case LosuTypeDefine_string:
            makeWidth(s, obj_tostr(vm, o), infos);
            break;
        }
    }

    static void addDecimal(std::string & res, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        char tmp[128];
        switch (obj_type(vm, o)) {
        case LosuTypeDefine_number:
            snprintf(tmp, sizeof(tmp), "%lld", (int64_t)obj_tonum(vm, o));
            res += tmp;
            break;
        }
    }

    static void addFloat(std::string & res, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        char tmp[128];
        switch (obj_type(vm, o)) {
        case LosuTypeDefine_number:
            if (infos.size() == 0) {
                snprintf(tmp, sizeof(tmp), "%.2llf", obj_tonum(vm, o));
                res += tmp;
            } else {
                uint64_t point = 2;
                bool pecent = false;
                char fs[64];

                for (int i = 0; i < infos.size(); i ++) {
                    std::string & s = infos[i];
                    if (s[0] == '.') {
                        sscanf(s.c_str() + 1, "%llu", &point);
                    } else if (s == "%") {
                        pecent = true;
                    }
                }

                if (pecent) {
                    double n = obj_tonum(vm, o);
                    snprintf(fs, sizeof(fs), "%%.%llullf%%%%", point);
                    snprintf(tmp, sizeof(tmp), fs, n * 100);
                } else {
                    snprintf(fs, sizeof(fs), "%%.%llullf", point);
                    snprintf(tmp, sizeof(tmp), fs, obj_tonum(vm, o));
                }
                makeWidth(res, tmp, infos);
            }
            break;
        }
    }

    static void addHex(std::string & res, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        char tmp[128];
        switch (obj_type(vm, o)) {
        case LosuTypeDefine_number:
            snprintf(tmp, sizeof(tmp), "%x", (int64_t)obj_tonum(vm, o));
            makeWidth(res, tmp, infos);
            break;
        }
    }
    
    static void addPtr(std::string & res, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        char tmp[128];
        switch (obj_type(vm, o)) {
        case LosuTypeDefine_number:
            snprintf(tmp, sizeof(tmp), "%p", (void*)(uint64_t)obj_tonum(vm, o));
            break;
        case LosuTypeDefine_ptr:
            snprintf(tmp, sizeof(tmp), "%p", obj_toptr(vm, o));
            break;
        case LosuTypeDefine_function:
            snprintf(tmp, sizeof(tmp), "%p", obj_tofunction(vm, o));
            break;
        default:
            return;
        }
        res += tmp;
        makeWidth(res, tmp, infos);
    }

    // 将n转化为b进制字符串，追加到s后
    static void itoa(int64_t n, std::string & s, int b) {
        std::string tmp = "";
        const static char table[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        if (n == 0) {
            s += "0";
            return;
        }
        while (n != 0) {
            int64_t t = n % b;
            tmp += table[t];
            n /= 2;
        }
        for (int i = tmp.length() - 1; i >= 0; i --) {
            s += tmp[i];
        }
    }

    static void addBin(std::string & res, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        switch (obj_type(vm, o)) {
            case LosuTypeDefine_number: {
                int64_t n = obj_tonum(vm, o);
                std::string s;
                itoa(n, s, 2);
                makeWidth(res, s.c_str(), infos);
                break;
            }
        }
    }

    static void addOctal(std::string & res, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        switch (obj_type(vm, o)) {
            case LosuTypeDefine_number: {
                int64_t n = obj_tonum(vm, o);
                char tmp[128];
                snprintf(tmp, sizeof(tmp), "%o", n);
                makeWidth(res, tmp, infos);
                break;
            }
        }
    }

    static void addAny(std::string & res, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        switch (obj_type(vm, o)) {
        case LosuTypeDefine_string:
            addStr(res, vm, o, infos);
            break;
        case LosuTypeDefine_number:
            addFloat(res, vm, o, infos);
            break;
        case LosuTypeDefine_ptr:
        case LosuTypeDefine_function:
            addPtr(res, vm, o, infos);
            break;
        }
    }

    static void addUnit_convert(std::string & s, LosuVm *vm, LosuObj *o) {
        switch (obj_type(vm, o)) {
            case LosuTypeDefine_string: {
                auto os = obj_tostr(vm, o);
                s += "\"";
                for (int i = 0; i < obj_getstrlen(vm, o); i ++) {
                    char ch = os[i];

                    switch (ch) {
                        case '\n':
                            s += "\\n";
                            break;
                        case '\t':
                            s += "\\t";
                            break;
                        case '\0':
                            s += "\\0";
                            break;
                        default: {
                            if (isprint(ch)) {
                                s += ch;
                            } else {
                                char tmp[64];
                                snprintf(tmp, sizeof(tmp), "\\%lld", (int64_t)ch);
                                s += tmp;
                            }
                        }
                    }
                }
                s+= "\"";
                break;
            }
            case LosuTypeDefine_number: {
                double n = obj_tonum(vm, o);
                char tmp[512];
                if (modf(n, nullptr) == 0) {
                    snprintf(tmp, sizeof(tmp), "%lld", (int64_t)n);
                    s = tmp;
                } else {
                    snprintf(tmp, sizeof(tmp), "%.16llf", n);
                    s = tmp;
                }
                break;
            }
        }
    }

    static void addUnit(std::string & res, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos) {
        if (obj_type(vm, o) != LosuTypeDefine_unit) {
            return;
        }

        res = "{";

        for (LosuNode *node = obj_unit_first(vm, *o); node != nullptr; node = obj_unit_next(vm, *o, node)) {
            LosuObj key, val;
            std::string s;

            key = obj_unit_nodekey(vm, node);
            val = obj_unit_nodevalue(vm, node);

            addUnit_convert(s, vm, &key);
            res += '[';
            res += s;
            res += ']';
            s.clear();
            addUnit_convert(s, vm, &val);
            res += ':';
            res += s;
            res += ',';
            s.clear();
        }

        res += '}';
    }

    static void addAny(std::string & res, LosuVm *vm, LosuObj *o) {
        std::vector<std::string> infos;
        addAny(res, vm, o, infos);
    }

    static void contentSplit(std::string & content, std::string & type, std::string & info) {
        uint64_t index = 0;

        type = "";
        info = "";

        for (; index < content.length(); index ++) {
            char ch = content[index];

            if (ch == ':') {
                index ++;
                break;
            }

            type += ch;
        }

        for (; index < content.length(); index ++) {
            char ch =  content[index];

            info += ch;
        }
    }

    static void infoSplit(std::string & info, std::vector<std::string> & infos) {
        uint64_t index = 0;
        std::string s = "";

        for (; index < info.length(); index ++) {
            char ch = info[index];
            if (ch == ',') {
                infos.push_back(s);
                s = "";
                continue;
            }
            s += ch;
        }

        if (s.length() != 0) {
            infos.push_back(s);
        }
    }

    typedef struct {
        const char *name;
        void (*func)(std::string & s, LosuVm *vm, LosuObj *o, std::vector<std::string> & infos);
    } TypeFunc;

    static const TypeFunc TF[] = {
        // 简称区
        {"s", addStr},
        {"n", addFloat},
        {"f", addFloat},
        {"d", addDecimal},
        {"x", addHex},
        {"p", addPtr},
        {"b", addBin},
        {"o", addOctal},

        // 全称区
        {"str", addStr},
        {"number", addFloat},
        {"float", addFloat},
        {"int", addDecimal},
        {"hex", addHex},
        {"ptr", addPtr},
        {"bin", addBin},
        {"octal", addOctal}
        //{"unit", addUnit}
    };

    LosuObj format(LosuVm *vm, LosuObj *fs, LosuObj *u) {
        char *fss = (char*)obj_tostr(vm, fs), *next = nullptr;
        std::string res = "";
        char ch;
        int index = 1;

        for (;;) {
            next = search(fss, '{');
            ch = *next;

            if (ch == '\0') {
                res += fss;
                break;
            }
            
            for (char *p = fss; p < next; p ++) {
                res += *p;
            }
            
            ch = next[1];
            next ++;

            if (ch == '}' || ch == '\0') {
                addAny(res, vm, obj_indexunitbynum(vm, *u, index));
                index ++;
                next ++;
            } else {
                std::string content = "";
                std::string type, info;
                std::vector<std::string> infos;
                bool know = false;

                for (char *p = next; ; p ++) {
                    char word = *p;
                    if (word == '\0') {
                        next = p;
                        break;
                    } else if (word == '}') {
                        next = p + 1;
                        break;
                    }
                    content += word;
                }

                contentSplit(content, type, info);
                infoSplit(info, infos);

                if (type.length() == 0) {
                    addAny(res, vm, obj_indexunitbynum(vm, *u, index), infos);
                    index ++;
                    continue;
                }

                // // 用数字索引的功能，因为在逻辑上不通顺，暂时注释掉
                // if (isdigit(type[0])) {
                //     int i;
                //     sscanf(type.c_str(), "%u", &i);
                //     addAny(res, vm, obj_indexunitbynum(vm, *u, i), infos);
                //     fss = next;
                //     continue;
                // }

                for (int i = 0; i < (sizeof(TF) / sizeof(TypeFunc)); i ++) {
                    const TypeFunc & tf = TF[i];

                    if (type == tf.name) {
                        tf.func(res, vm, obj_indexunitbynum(vm, *u, index), infos);
                        know = true;
                        break;
                    }
                }

                if (!know) {
                    char tmp[128];
                    snprintf(tmp, sizeof(tmp), "Unknow type at %llu", index);
                    vm_warning(vm, tmp);
                }

                index ++;
	        }

            fss = next;
        }

        return obj_newstr(vm, (char*)res.c_str());
    }

    LosuObj itoa(LosuVm *vm, LosuObj *n, int b) {
        int64_t d = obj_tonum(vm, n);
        std::string s = "";
        fm::itoa(d, s, b);
        return obj_newstr(vm, (char*)s.c_str());
    }
} // namespace fm

extern "C" int format(LosuVm *vm) {
    LosuObj *fs, *u;

    fs = arg_get(vm, 1);
    u = arg_get(vm, 2);

    arg_return(vm, fm::format(vm, fs, u));
    return 1;
}

